#ifndef NET_CONTEXT_H
#define NET_CONTEXT_H

#include <iostream>
#include <string>
#include <memory>
#include <mutex>
#include <unordered_map>

#include <stdio.h>
#include <assert.h>
#include <inttypes.h>

#ifndef NATIVE_SOCKET
#include "num_pool.h"
#include "ifconfig.h"
#include "arp_impl.h"
#include "endpoint_addr.h"
#endif

#ifdef LOG_DEBUG
#undef LOG_DEBUG
#endif
#define LOG_DEBUG(ctx, format, ...) {                       \
    if (ctx->LogLevel() <= DEBUG) {                         \
        sprintf(ctx->GetLogMsg(), format, ##__VA_ARGS__);   \
        ctx->PrintLog();                                    \
    }                                                       \
}

#ifdef LOG_ERROR
#undef LOG_ERROR
#endif
#define LOG_ERROR(ctx, format, ...) {                       \
    if (ctx->LogLevel() <= ERROR) {                         \
        sprintf(ctx->GetLogMsg(), format, ##__VA_ARGS__);   \
        ctx->PrintLog();                                    \
    }                                                       \
}

using PrintFunc = void(*)(const char*, void* data);

namespace usnet {
constexpr int MAX_LOG_LEN   { 1024 };

enum LogLevel : uint32_t {
    DEBUG = 0,
    ERROR = 1,
    NOLOG = 2,
};

class NetContext {
public:
    ~NetContext() = default;

    /**************************************************************
     * init, get and destroy context
     */
    static bool Init();
    inline static NetContext* GetContext()
    {
        return net_ctx_;
    }
    inline static void Destroy()
    {
        if (net_ctx_) {
            delete net_ctx_;
        }
        net_ctx_ = nullptr;
    }
    /**************************************************************/

    /**************************************************************
     * internal log interfaces
     */
    inline void SetLogLevel(const uint32_t level)
    {
        log_info_.level_ = level;
    }
    inline uint32_t LogLevel() const
    {
        return log_info_.level_;
    }
    inline char* GetLogMsg()
    {
        return log_info_.msg_;
    }
    inline void PrintLog()
    {
        std::cerr << log_info_.msg_ << std::endl;
    }
    /**************************************************************/

#ifndef NATIVE_SOCKET
    /**************************************************************
     * fd managment
     */
    inline int GetConnFD()
    {
        return fd_mgr_.GetNumber();
    }
    inline void PutConnFD(const int fd)
    {
        return fd_mgr_.PutNumber(static_cast<uint32_t>(fd));
    }
    /**************************************************************/

    /**************************************************************
     * port number managment
     */
    inline bool GetPort(const uint16_t port)
    {
        return port_mgr_.GetNumber(static_cast<uint32_t>(port));
    }
    inline void PutPort(const uint16_t port)
    {
        return port_mgr_.PutNumber(static_cast<uint32_t>(port));
    }
    /**************************************************************/
    inline bool CreateARPImpl(const Ifconfig* ifcfg, const EndpointAddr* addr)
    {
        if (ifcfg->ifindex_) {
            if (arp_impl_tab_.count(ifcfg->ifindex_) == 0) {
                auto arp_impl = ARPImpl::MakeUnique(ifcfg, addr);
                if (arp_impl == nullptr) {
                    return false;
                }
                arp_impl_tab_[ifcfg->ifindex_] = std::move(arp_impl);
            }
            return true;
        }
        return false;
    }

    inline  bool GetEthAddr(const Ifconfig* ifcfg, EndpointAddr* addr) const
    {
        assert(arp_impl_tab_.count(ifcfg->ifindex_));
        return arp_impl_tab_.at(ifcfg->ifindex_)->GetEthAddr(addr);
    }

    /**************************************************************
     * network interface lookup
     */
    const Ifconfig* GetIfconfig(const EndpointAddr* addr) const;
    /**************************************************************/

    inline FILE* GetDumpFP() const
    {
        // return dump_fp_;
        return nullptr;
    }
#endif

private:
    NetContext() = default;

    bool InitIfconfig();

    struct {
        uint32_t level_ { NOLOG };
        char msg_[MAX_LOG_LEN];
    } log_info_;

#ifndef NATIVE_SOCKET
    FILE* dump_fp_ { nullptr };
    NumPool fd_mgr_     { 16 };
    NumPool port_mgr_   { 16 };
    std::unordered_map<std::string, Ifconfig> ifcfg_tab_ { };
    std::unordered_map<uint32_t, std::unique_ptr<ARPImpl>> arp_impl_tab_ { };
#endif

    static std::mutex mtx_;
    static NetContext* net_ctx_;
};

} // end of namespace usnet

#endif